// (C) Uri Wilensky. https://github.com/NetLogo/NetLogo

package org.nlogo.window;

import org.nlogo.api.I18N;
import org.nlogo.shape.VectorShape;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JToggleButton;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Enumeration;

public strictfp class ColorDialog extends JDialog implements ActionListener,
    ItemListener,
    MouseListener,
    ClipboardOwner,
    WindowListener {

  private double step = 1; // The increment of the color index incremented  patches
  private boolean numberVisibilityFlag = false; // Flat to view color value on swatches
  //To remember the visibility after change by itemStateChanged generated by setSelected
  private boolean pointOneStepflag = false;

  private final NumberFormat formatter = new DecimalFormat("###.####");

  private int okCancelFlag = 0;
  private boolean plotPenMode = false;
  private JButton okButton;

  private Color selectedColor;
  private double selectedColorNumber;
  private String selectedColorName;

  private static final int SIZE_X = 600;
  private static final int SIZE_Y = 400;

  private final Container pane;
  private final ButtonGroup swatchesGroup = new ButtonGroup();
  private final Container swatchesContainer = new Container();
  private final Container leftNameContainer = new Container();
  private final Container topNameContainer = new Container();
  private final Container rightPreviewContainer = new Container();

  private JRadioButton oneStep;
  private JRadioButton pointFiveStep;
  private JRadioButton pointOneStep;

  private ImageIcon turtleIcon;
  private JCheckBox checkboxHideNumbers;
  private JLabel selectedColorLabel;
  private JLabel[] turtleLabel = new JLabel[16];

  public ColorDialog(Frame frame, boolean modalFlag) {
    super(frame, I18N.guiJ().get("tools.colorswatch"), modalFlag);
    setVisible(false);

    getContentPane().setLayout(new GridBagLayout());
    setSize(SIZE_X, SIZE_Y);
    setDefaultCloseOperation(HIDE_ON_CLOSE);

    pane = getContentPane();
    turtleIcon = getShapeIcon(0);

    addWindowListener(this);

    createLeftNames();
    createTopNames(turtleIcon);
    createRightPreview(turtleIcon);
    createTopLeftLabel();
    createSwatches();
    org.nlogo.swing.Utils.addEscKeyAction
        (this,
            new javax.swing.AbstractAction() {
              public void actionPerformed(java.awt.event.ActionEvent e) {
                setVisible(false);
                dispose();
              }
            });
  }

  public void showDialog() {
    plotPenMode = false;

    createControls(plotPenMode);
    selectSwatch(Color.black);
    workAroundQuaquaBug(this);
    setVisible(true);
  }


  public Color showPlotPenDialog(Color initialColor) {
    plotPenMode = true;

    createControls(plotPenMode);
    selectSwatch(initialColor);
    workAroundQuaquaBug(this);
    setVisible(true);

    if (okCancelFlag == 1) {
      return selectedColor;
    } else {
      return null;
    }
  }

  public double showInputBoxDialog(double initialColor) {
    plotPenMode = true;
    createControls(plotPenMode);
    selectSwatch(org.nlogo.api.Color.getColor(initialColor));
    workAroundQuaquaBug(this);
    setVisible(true);
    if (okCancelFlag == 1) {
      return selectedColorNumber;
    } else {
      return initialColor;
    }
  }

  private void selectSwatch(Color initialColor) {
    double closest = org.nlogo.api.Color.getClosestColorNumberByARGB(initialColor.getRGB());
    Color closestColor = new Color(org.nlogo.api.Color.getARGBbyPremodulatedColorNumber(closest));

    if (closest % 1 == 0 || closest >= 9.9) {
      oneStep.doClick();
    } else if (closest % 5 == 0) {
      pointOneStep.doClick();
    } else {
      pointFiveStep.doClick();
    }

    for (Enumeration<javax.swing.AbstractButton> e = swatchesGroup.getElements(); e.hasMoreElements();) {
      JToggleButton swatch = (JToggleButton) e.nextElement();
      Color color = swatch.getBackground();
      if (closestColor.equals(color)) {
        swatch.doClick();
      }
    }
  }

  /// Interface Creation

  // Create the Black and White Buttons at the top
  private void createTopNames(ImageIcon turtleIcon) {
    topNameContainer.setLayout(new GridLayout());
    // Create the black button
    Container blackButtonContainer = new Container();
    blackButtonContainer.setLayout(new GridLayout(1, 3));

    turtleLabel[14] = new JLabel(turtleIcon, javax.swing.SwingConstants.CENTER);
    turtleLabel[14].setPreferredSize(new Dimension(60, 20));
    turtleLabel[14].setBackground(new java.awt.Color(0, 0, 0));
    turtleLabel[14].setOpaque(true);
    blackButtonContainer.add(turtleLabel[14]);

    JToggleButton blackName = new JToggleButton(org.nlogo.api.Color.getColorNameByIndex(14) + " = 0");
    blackName.addActionListener(this);
    blackName.setActionCommand("0.0");
    blackName.setOpaque(true);
    blackName.setFont(new java.awt.Font("ArialNarrow", 0, 10));
    Insets insets = new Insets(0, 0, 0, 0);
    blackName.setMargin(insets);
    swatchesGroup.add(blackName);
    blackButtonContainer.add(blackName);
    blackButtonContainer.add(new JLabel(""));
    topNameContainer.add(blackButtonContainer);

    // Create the white button
    Container whiteButtonContainer = new Container();
    whiteButtonContainer.setLayout(new GridLayout(1, 3));

    whiteButtonContainer.add(new JLabel(""));

    JToggleButton whiteName = new JToggleButton(org.nlogo.api.Color.getColorNameByIndex(15) + " = 9.9");
    whiteName.addActionListener(this);
    whiteName.setActionCommand("9.9");
    whiteName.setOpaque(true);
    whiteName.setFont(new java.awt.Font("ArialNarrow", 0, 10));
    whiteName.setMargin(insets);
    swatchesGroup.add(whiteName);
    whiteButtonContainer.add(whiteName);

    turtleLabel[15] = new JLabel(turtleIcon, javax.swing.SwingConstants.CENTER);
    turtleLabel[15].setPreferredSize(new Dimension(60, 20));
    turtleLabel[15].setBackground(new java.awt.Color(255, 255, 255));
    turtleLabel[15].setOpaque(true);
    whiteButtonContainer.add(turtleLabel[15]);
    topNameContainer.add(whiteButtonContainer);

    // Add the container to the GridBag
    GridBagConstraints c = new GridBagConstraints();
    c.gridx = 1;
    c.gridy = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    pane.add(topNameContainer, c);
    getContentPane().validate();
  }

  private void createLeftNames() {

    leftNameContainer.setLayout(new GridLayout(14, 1));

    for (int i = 5; i < 140; i += 10) {
      // Create name Buttons
      String colorNameString = org.nlogo.api.Color.getColorNameByIndex(i / 10) + " = " + i;
      JToggleButton colorName = new JToggleButton(colorNameString);
      colorName.addActionListener(this);
      colorName.setActionCommand(String.valueOf(i));
      colorName.setFont(new java.awt.Font("ArialNarrow", 0, 10));
      swatchesGroup.add(colorName);
      leftNameContainer.add(colorName);
    }
    getContentPane().invalidate();

    GridBagConstraints c = new GridBagConstraints();
    c.gridx = 0;
    c.gridy = 1;
    c.fill = GridBagConstraints.VERTICAL;
    c.insets = new Insets(5, 0, 0, 0);
    pane.add(leftNameContainer, c);
  }

  private void createRightPreview(ImageIcon turtleIcon) {
    rightPreviewContainer.setLayout(new GridLayout(14, 1));

    int turtleIconindex = 0;
    for (int i = 5; i < 140; i += 10) {
      // Create color turtles with variable background
      turtleLabel[turtleIconindex] = new JLabel(turtleIcon, javax.swing.SwingConstants.CENTER);
      turtleLabel[turtleIconindex].setBackground(new Color(org.nlogo.api.Color.getARGBbyPremodulatedColorNumber(i)));
      turtleLabel[turtleIconindex].setOpaque(true);
      rightPreviewContainer.add(turtleLabel[turtleIconindex]);
      turtleIconindex++;
    }
    getContentPane().invalidate();

    GridBagConstraints c = new GridBagConstraints();
    c.gridx = 2;
    c.gridy = 1;
    c.fill = GridBagConstraints.BOTH;
    c.insets = new Insets(5, 0, 0, 0);
    pane.add(rightPreviewContainer, c);
  }

  private void createTopLeftLabel() {
    JLabel upperLeftCorner = new JLabel(I18N.guiJ().get("tools.colorswatch.preview"));

    upperLeftCorner.setFont(new java.awt.Font("ArialNarrow", 0, 10));
    upperLeftCorner.setPreferredSize(new Dimension(45, 20));

    GridBagConstraints c = new GridBagConstraints();
    c.gridx = 2;
    c.gridy = 0;
    c.insets = new Insets(0, 2, 0, 0);
    pane.add(upperLeftCorner, c);
  }

  private void createSwatches() {
    final int rows = 14;
    double colorNumber = 0;
    int columnlength = 10;
    int swatchPerRow = (int) (StrictMath.round(columnlength / step));


    swatchesContainer.invalidate();

    swatchesContainer.setLayout(new GridLayout(rows, swatchPerRow));

    for (int i = 0; i < rows; i++) {
      colorNumber = i * 10;
      for (int j = 0; j < (swatchPerRow + 1); j++) {
        JToggleButton swatch = new JToggleButton();
        if (j >= swatchPerRow) {
          colorNumber = (i * 10) + 9.9;
        } else {
          swatch.setForeground(Color.BLACK);
        }
        swatch.setRolloverEnabled(true);
        swatch.setBorderPainted(true);
        swatch.setOpaque(true);
        swatch.setFocusPainted(false);
        swatch.setBorder(BorderFactory.createEmptyBorder());

        // I don't entirely understand what's going on here
        // on xp l&f java 1.5 the background color of the toggle
        //  buttons doesn't show up so we work around using
        // a color swatch (which is really just a Jpanel)
        // that works on windows but doesn't on mac.
        // however, setting the background of the button
        // seems to work fine so just do both. ev 3/17/06
        Color c = new Color
            (org.nlogo.api.Color.getARGBbyPremodulatedColorNumber
                (colorNumber));

        swatch.setBackground(c);
        org.nlogo.swing.ColorSwatch s = new org.nlogo.swing.ColorSwatch
            (swatch.getPreferredSize().width,
                swatch.getPreferredSize().height);
        s.setBackground(c);

        if (!numberVisibilityFlag && !pointOneStepflag) {
          JLabel label = new JLabel(formatter.format(colorNumber));
          label.setFont(new java.awt.Font("ArialNarrow", 0, 8));
          if (colorNumber % 10 < 3.5) {
            label.setForeground(Color.LIGHT_GRAY);
          }
          s.add(label);
        }
        swatch.add(s);
        swatch.setActionCommand(String.valueOf(colorNumber));
        swatch.addActionListener(this);
        swatch.addMouseListener(this);

        swatchesGroup.add(swatch);
        swatchesContainer.add(swatch);

        colorNumber = step + colorNumber;
      }
    }
    swatchesContainer.validate();

    GridBagConstraints c = new GridBagConstraints();
    c.gridx = 1;
    c.gridy = 1;
    c.weightx = 1.0;
    c.weighty = 1.0;
    c.fill = GridBagConstraints.BOTH;
    c.anchor = GridBagConstraints.CENTER;
    c.insets = new Insets(5, 5, 0, 2);

    pane.add(swatchesContainer, c);
  }

  private void createControls(boolean plotPenFlag) {
    Container controlsContainer = new Container();
    controlsContainer.setLayout(new BoxLayout(controlsContainer, BoxLayout.LINE_AXIS));
    // Copy button
    if (!plotPenFlag) {
      JButton copyButton = new JButton(I18N.guiJ().get("tools.colorswatch.copy"));
      copyButton.setActionCommand("Copy");
      copyButton.addActionListener(this);
      controlsContainer.add(copyButton);
    } else {
      controlsContainer.add(Box.createRigidArea(new Dimension(10, 0)));
      okButton = new JButton(I18N.guiJ().get("common.buttons.ok"));
      controlsContainer.add(okButton);
      okButton.addActionListener(this);
      controlsContainer.add(Box.createRigidArea(new Dimension(10, 0)));
      JButton cancelButton = new JButton(I18N.guiJ().get("common.buttons.cancel"));
      cancelButton.addActionListener(this);
      controlsContainer.add(cancelButton);
      controlsContainer.add(Box.createRigidArea(new Dimension(10, 0)));
      //JLabel selectedColorLabel = new JLabel("selected color");
      //controlsContainer.add(selectedColorLabel);
    }
    // selected Color Label
    selectedColorLabel = new JLabel(org.nlogo.api.Color.getColorNameByIndex(14));
    selectedColorLabel.setMinimumSize(new Dimension(100, 20));
    selectedColorLabel.setMaximumSize(new Dimension(100, 20));
    selectedColorLabel.setPreferredSize(new Dimension(100, 20));
    selectedColorLabel.setBorder(BorderFactory.createLoweredBevelBorder());
    controlsContainer.add(selectedColorLabel);

    controlsContainer.add(Box.createHorizontalGlue());

    // Hide numbers checkbox
    checkboxHideNumbers = new JCheckBox(I18N.guiJ().get("tools.colorswatch.numbers"), true);
    controlsContainer.add(checkboxHideNumbers);
    checkboxHideNumbers.addItemListener(this);

    controlsContainer.add(Box.createHorizontalGlue());

    // Radio buttons

    JPanel stepJpanel = new JPanel();
    stepJpanel.setLayout(new BoxLayout(stepJpanel, BoxLayout.LINE_AXIS));
    stepJpanel.setBorder(BorderFactory.createLineBorder(Color.gray));

    oneStep = new JRadioButton("1");
    oneStep.setActionCommand("oneStep");
    stepJpanel.add(oneStep);
    oneStep.setSelected(true);

    pointFiveStep = new JRadioButton("0.5");
    pointFiveStep.setActionCommand("pointFiveStep");
    stepJpanel.add(pointFiveStep);

    pointOneStep = new JRadioButton("0.1");
    pointOneStep.setActionCommand("pointOneStep");
    stepJpanel.add(pointOneStep);

    ButtonGroup groupStep = new ButtonGroup();
    groupStep.add(oneStep);
    groupStep.add(pointFiveStep);
    groupStep.add(pointOneStep);

    oneStep.addActionListener(this);
    pointFiveStep.addActionListener(this);
    pointOneStep.addActionListener(this);

    controlsContainer.add(stepJpanel);

    JLabel gradationLabel = new JLabel(" " + I18N.guiJ().get("tools.colorswatch.increment"));
    controlsContainer.add(gradationLabel);

    controlsContainer.add(Box.createRigidArea(new Dimension(10, 0)));
    controlsContainer.add(new JLabel("  "));

    GridBagConstraints c = new GridBagConstraints();
    c.gridx = 0;
    c.gridy = 2;
    c.fill = GridBagConstraints.HORIZONTAL;
    c.gridwidth = 3;
    pane.add(controlsContainer, c);
  }


  /// Auxiliary Methods

  // Create turtle icon
  private ImageIcon getShapeIcon(double colorValue) {
    VectorShape defaultShape = org.nlogo.shape.VectorShape.getDefaultShape();
    BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
    org.nlogo.api.Graphics2DWrapper g2 = new org.nlogo.api.Graphics2DWrapper(image.createGraphics());
    g2.antiAliasing(true);
    defaultShape.paint
        (g2,
            new Color(org.nlogo.api.Color.getARGBbyPremodulatedColorNumber(colorValue)),
            0, 0, 16, 0);
    g2.drawImage(image);
    return new ImageIcon(image);
  }

  // Check if the passed String is a number
  private boolean isNumber(String n) {
    try {
      Double.valueOf(n).doubleValue();
      return true;
    } catch (NumberFormatException e) {
      return false;
    }
  }

  public void swatchAction() {


  }

  /// Event Handling

  public void actionPerformed(ActionEvent e) {
    String actionCommand = e.getActionCommand();
    if (isNumber(actionCommand)) // Select a Color Swatch
    {
      selectedColorNumber = Double.valueOf(e.getActionCommand()).doubleValue();
      selectedColor = new Color(org.nlogo.api.Color.getARGBbyPremodulatedColorNumber(selectedColorNumber));
      selectedColorName = org.nlogo.api.Color.getClosestColorNameByARGB(selectedColor.getRGB());
      selectedColorLabel.setText(selectedColorName);
      turtleIcon = getShapeIcon(selectedColorNumber);
      for (int i = 0; i <= 15; i++) {
        turtleLabel[i].setIcon(turtleIcon);
      }
    } else if (actionCommand.equals("Copy")) // Copy the Color value to the Clipboard
    {
      Toolkit tk = Toolkit.getDefaultToolkit();
      StringSelection st = new StringSelection(selectedColorName);
      Clipboard cp = tk.getSystemClipboard();
      cp.setContents(st, this);
    } else if (actionCommand.endsWith("Step")) // Change Swaches size
    {
      if (actionCommand.equals("oneStep")) {
        step = 1;
        pointOneStepflag = false;
        checkboxHideNumbers.setEnabled(true);
      } else if (actionCommand.equals("pointFiveStep")) {
        step = .5;
        pointOneStepflag = false;
        checkboxHideNumbers.setEnabled(true);

      } else if (actionCommand.equals("pointOneStep")) {
        step = .1;
        pointOneStepflag = true;
        checkboxHideNumbers.setEnabled(false);
      }
      swatchesContainer.setVisible(false);
      swatchesContainer.removeAll();
      createSwatches();
      swatchesContainer.setVisible(true);
      swatchesContainer.repaint();
    } else if (actionCommand.equals(I18N.guiJ().get("common.buttons.ok"))) {
      okCancelFlag = 1;
      dispose();
    } else if (actionCommand.equals(I18N.guiJ().get("common.buttons.cancel"))) {
      okCancelFlag = -1;
      dispose();
    }
  }

  // Hide or show color values
  public void itemStateChanged(ItemEvent e) {
    if (e.getStateChange() == ItemEvent.SELECTED) {
      numberVisibilityFlag = false;
    } else if (e.getStateChange() == ItemEvent.DESELECTED) {
      numberVisibilityFlag = true;
    }
    swatchesContainer.removeAll();
    createSwatches();
  }

  // Illuminate the patch under the mouse cusor as it moves.
  public void mouseEntered(MouseEvent e) {

    JToggleButton selectedSwatch = (JToggleButton) e.getSource();
    selectedSwatch.setBorder(BorderFactory.createLineBorder(Color.gray));

    // Why does (e.getModifiers()) return 16 instead of
    // 503 which is equal to MouseEvent.MOUSE_PRESSED
    // Thus (e.getModifiers() & MouseEvent.MOUSE_PRESSED) should return 503?
    if ((e.getModifiers() & MouseEvent.MOUSE_PRESSED) == 16) {
      selectedSwatch.doClick();
    }
  }

  public void mouseExited(MouseEvent e) {
    JToggleButton selectedSwatch = (JToggleButton) e.getSource();
    selectedSwatch.setSelected(false);
    selectedSwatch.setBorder(BorderFactory.createEmptyBorder());
  }

  public void windowClosing(WindowEvent e) {
    // I check the flag below to see if we are in PlotPenMode
    // In that case closing the window is the same as pressing OK
    // This is a different behavior than in the "MenuToolsMode"
    if (plotPenMode) {
      okCancelFlag = 1;
      okButton.doClick();
    }
  }

  // The following callback are not used for anything
  // but the Interfaces demands them
  public void mouseDragged(MouseEvent e) {
  }

  public void mouseClicked(MouseEvent e) {
  }

  public void mousePressed(MouseEvent e) {
  }

  public void mouseReleased(MouseEvent e) {
  }

  public void lostOwnership(Clipboard clip, Transferable tr) {
  }

  public void componentMoved(ComponentEvent arg0) {
  }

  public void componentShown(ComponentEvent arg0) {
  }

  public void componentHidden(ComponentEvent arg0) {
  }

  public void windowOpened(WindowEvent arg0) {
  }

  public void windowClosed(WindowEvent arg0) {
  }

  public void windowIconified(WindowEvent arg0) {
  }

  public void windowDeiconified(WindowEvent arg0) {
  }

  public void windowActivated(WindowEvent arg0) {
  }

  public void windowDeactivated(WindowEvent arg0) {
  }

  // work around bug in Quaqua 6.5 causing NullPointerExceptions in
  // QuaquaButtonUI.getMinimumSize() - ST 5/12/10

  public void workAroundQuaquaBug(java.awt.Container root) {
    org.nlogo.awt.Tree.walkComponentTree
        (root, 0,
            new org.nlogo.awt.Tree.ComponentTreeWalker() {
              public void touch(java.awt.Component comp, int level) {
                if (comp instanceof javax.swing.JComponent) {
                  // using a different style than the default doesn't affect
                  // the visual appearance, and it avoids the code path
                  // that was causing the exceptions - ST 5/12/10
                  ((javax.swing.JComponent) comp).putClientProperty
                      ("Quaqua.Button.style", "toggle");
                }
              }
            });
  }

}
